package com.giscloud.redis.config;

import com.google.common.collect.Sets;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.util.Set;

/**
 * @Author:giscloud
 * @Description:缓存配置-使用Lettuce客户端，自动注入配置的方式 哨兵模式
 * @Date: 10:54 2019/7/17
 * @Modified By:
 */
@Configuration
@EnableCaching //启用缓存
@Log4j2
@ConditionalOnProperty(prefix = "spring.redis", name = "type", havingValue = "sentinel")
public class SentinelRedisConfig extends CachingConfigurerSupport {

    @Value("${spring.redis.database:0}")
    private int database;
    @Value("${spring.redis.cluster.nodes:localhost}")
    private String clusterNodes;
    @Value("${spring.redis.cluster.max-redirects:3}")
    private int maxRedirects;

    @Value("${spring.redis.host:localhost}")
    private String host;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port:6379}")
    private int port;

    @Value("${spring.redis.timeout}")
    private long timeout;

    @Value("${spring.redis.lettuce.pool.max-idle:1}")
    private int maxIdle;

    @Value("${spring.redis.lettuce.pool.min-idle:1}")
    private int minIdle;

    @Value("${spring.redis.lettuce.pool.max-active:1}")
    private int maxActive;

    @Value("${spring.redis.lettuce.pool.max-wait:0}")
    private long maxWait;

    @Value("${spring.redis.sentinel.master:mymaster}")
    private String sentinelMaster;

    @Value("${spring.redis.sentinel.nodes: localhost}")
    private String sentinelNodes;

    @Value("${spring.redis.type}")
    private String redisType;
    /**
     * 自定义缓存key的生成策略。默认的生成策略是看不懂的(乱码内容) 通过Spring 的依赖注入特性进行自定义的配置注入并且此类是一个配置类可以更多程度的自定义配置
     *
     * @return
     */
    @Value("${spring.cache.redis.key-prefix}")
    private String keyPrefix;
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                for (Object o : params) {
                    sb.append(":").append(o.toString());
                }
                return keyPrefix+":"+method.getName()+sb.toString();
            }
        };
    }

    /**
     * 缓存配置管理器
     */
    @Override
    @Bean
    public CacheManager cacheManager() {
        //以锁写入的方式创建RedisCacheWriter对象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory());
        /*
        设置CacheManager的Value序列化方式为JdkSerializationRedisSerializer,
        但其实RedisCacheConfiguration默认就是使用
        StringRedisSerializer序列化key，
        JdkSerializationRedisSerializer序列化value,
        所以以下注释代码就是默认实现，没必要写，直接注释掉
         */
        // RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(this.getClass().getClassLoader()));
        // RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        //创建默认缓存配置对象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
        return cacheManager;
    }

    /**
     * 获取缓存操作助手对象
     *
     * spring-data-redis中序列化类有以下几个：
     GenericToStringSerializer：可以将任何对象泛化为字符创并序列化
     Jackson2JsonRedisSerializer：序列化Object对象为json字符创（与JacksonJsonRedisSerializer相同）
     JdkSerializationRedisSerializer：序列化java对象
     StringRedisSerializer：简单的字符串序列化
     JdkSerializationRedisSerializer序列化
     被序列化对象必须实现Serializable接口，被序列化除属性内容还有其他内容，长度长且不易阅读
     存储内容如下：
     "\xac\xed\x00\x05sr\x00!com.oreilly.springdata.redis.User\xb1\x1c \n\xcd\xed%\xd8\x02\x00\x02I\x00\x03ageL\x00\buserNamet\x00\x12Ljava/lang/String;xp\x00\x00\x00\x14t\x00\x05user1"
     JacksonJsonRedisSerializer序列化
     被序列化对象不需要实现Serializable接口，被序列化的结果清晰，容易阅读，而且存储字节少，速度快
     存储内容如下：
     "{\"userName\":\"user1\",\"age\":20}"
     * @return
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        Set<String> nodes = Sets.newHashSetWithExpectedSize(3);
        String[] sentinels = sentinelNodes.split(",");
        for (String sentinel : sentinels) {
            nodes.add(sentinel);
        }
        RedisSentinelConfiguration configuration = new RedisSentinelConfiguration(sentinelMaster,nodes);
        if(StringUtils.isNotBlank(password)) {
            configuration.setPassword(RedisPassword.of(password));
        }
        configuration.setDatabase(database);
        return new JedisConnectionFactory(configuration);
    }

    @Bean
    public CustomRedisTemplate<String, String> redisTemplate() {
        //创建Redis缓存操作助手RedisTemplate对象,StringRedisTemplate是RedisTempLate<String, String>的子类
        CustomRedisTemplate redisTemplate = new CustomRedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        StringRedisSerializer srSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        redisTemplate.setKeySerializer(srSerializer);
        // hash的key也采用String的序列化方式
        redisTemplate.setHashKeySerializer(srSerializer);
        // value序列化方式采用String的序列化方式
        redisTemplate.setValueSerializer(srSerializer);
        // hash的value序列化方式采用String的序列化方式
        redisTemplate.setHashValueSerializer(srSerializer);
        // 开启事务
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean(name="rateLimiterClientLua")
    public RedisScript<Long> rateLimiterClientLua() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setLocation(new ClassPathResource("rate_limiter.lua"));
        redisScript.setResultType(Long.class);
        return redisScript;
    }
    @Bean
    public CustomRedisTemplate stringRedisTemplate() {
        CustomRedisTemplate redisTemplate = new CustomRedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory());

        StringRedisSerializer srSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        redisTemplate.setKeySerializer(srSerializer);
        // hash的key也采用String的序列化方式
        redisTemplate.setHashKeySerializer(srSerializer);
        // value序列化方式采用String的序列化方式
        redisTemplate.setValueSerializer(srSerializer);
        // hash的value序列化方式采用String的序列化方式
        redisTemplate.setHashValueSerializer(srSerializer);
        // 开启事务
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
